package com.github.gaojh.ioc.bean;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Filter;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.StrUtil;
import com.github.gaojh.Flying;
import com.github.gaojh.aop.annotation.Aop;
import com.github.gaojh.ioc.annotation.*;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

/**
 * @author 高建华
 * @date 2019-04-28 21:25
 */
public class BeanClassScanner implements ClassScanner {

    private static final ConcurrentHashMap<String, ClassInfo> CLASS_INFO_MAP = new ConcurrentHashMap<>();

    public BeanClassScanner(List<String> basePackages) {
        List<Class<? extends Annotation>> annotationList = getAnnotationList(basePackages);
        for (String basePackage : basePackages) {
            for (Class<? extends Annotation> annotationClass : annotationList) {
                ClassUtil.scanPackageByAnnotation(basePackage, annotationClass).stream()
                        .filter(clazz -> !clazz.isAnnotation())
                        .filter(clazz -> !clazz.isInterface())
                        .filter(clazz -> !clazz.isEnum())
                        .forEach(this::addClass);
            }
        }
    }

    private List<Class<? extends Annotation>> getAnnotationList(List<String> basePackages) {
        List<Class<? extends Annotation>> annotationList = new ArrayList<>();
        for (String basePackage : basePackages) {
            ClassUtil.scanPackageByAnnotation(basePackage, Component.class).stream().filter(Class::isAnnotation).forEach(clazz -> {
                Class<? extends Annotation> c = (Class<? extends Annotation>) clazz;
                annotationList.add(c);
            });

        }
        annotationList.add(Component.class);
        return annotationList;
    }

    private List<Class<? extends Annotation>> getAnnotationList() {
        return CollectionUtil.toList(Configuration.class, Component.class, Controller.class, Setup.class, WebFilter.class, WebSocket.class, Aop.class);
    }

    @Override
    public ClassInfo getClassInfo(String name) {
        return CLASS_INFO_MAP.get(name);
    }

    @Override
    public ClassInfo getClassInfo(Class<?> clazz) {
        AtomicReference<ClassInfo> result = new AtomicReference<>();
        CLASS_INFO_MAP.forEach((k, v) -> {
            if (v.getBeanClass().equals(clazz)) {
                result.set(v);
            }
        });
        return result.get();
    }

    @Override
    public List<ClassInfo> getClassByAnnotation(Class<? extends Annotation> annotation) {
        return CLASS_INFO_MAP.values().stream().filter(classInfo -> classInfo.getBeanClass().isAnnotationPresent(annotation)).collect(Collectors.toList());
    }

    @Override
    public List<ClassInfo> getClassByInterface(Class<?> interfaceClass) {
        return CLASS_INFO_MAP.values().stream().filter(classInfo -> interfaceClass.isAssignableFrom(classInfo.getBeanClass())).collect(Collectors.toList());
    }

    @Override
    public List<ClassInfo> getAllClass() {
        return new ArrayList<>(CLASS_INFO_MAP.values());
    }

    @Override
    public String getBeanName(Class<?> clazz) {
        if (clazz.isInterface()) {
            List<ClassInfo> classes = getClassByInterface(clazz);
            if (classes == null || classes.isEmpty()) {
                return clazz.getName();
            } else if (classes.size() > 1) {
                String msg = String.format("该接口[%s]的实现类有%d个，应该只能是1个", clazz.getName(), classes.size());
                throw new RuntimeException(msg);
            } else {
                clazz = classes.get(0).getBeanClass();
            }
        }
        String name = clazz.getName();

        if (clazz.isAnnotationPresent(Component.class)) {
            Component component = clazz.getAnnotation(Component.class);
            name = StrUtil.isBlank(component.value()) ? name : component.value();
        } else if (clazz.isAnnotationPresent(Controller.class)) {
            Controller controller = clazz.getAnnotation(Controller.class);
            name = StrUtil.isBlank(controller.value()) ? name : controller.value();
        } else if (clazz.isAnnotationPresent(Configuration.class)) {
            Configuration configuration = clazz.getAnnotation(Configuration.class);
            name = StrUtil.isBlank(configuration.value()) ? name : configuration.value();
        }

        return name;
    }

    @Override
    public void addClass(Class<?> clazz) {
        String beanName = getBeanName(clazz);
        CLASS_INFO_MAP.put(beanName, ClassInfo.builder().beanClass(clazz).beanName(beanName).isConfigurationBean(false).build());
        if (clazz.isAnnotationPresent(Configuration.class)) {

            for (Method method : clazz.getDeclaredMethods()) {
                Bean bean = method.getAnnotation(Bean.class);
                if (bean == null) {
                    continue;
                }
                String methodBeanName = StrUtil.isBlank(bean.value()) ? getBeanName(method.getReturnType()) : bean.value();
                ClassInfo classDefine = ClassInfo.builder().beanName(methodBeanName).beanClass(method.getReturnType()).isConfigurationBean(true).configurationClass(clazz).method(method).build();
                CLASS_INFO_MAP.put(methodBeanName, classDefine);
            }
            ;
        }
    }
}
